pnpm은 npm의 문제를 어떻게 해결했을까
pnpm은 npm의 단점을 극복하고자 2017년에 등장한 패키지 매니저로
Content-Addressable Store라는 개념을 도입했다.
하위 의존성으로 lodash가 100번 사용된다면, 기존 npm에서는 100번을 설치해야 했다.
물론 v3에서는 버전이 같다면 하나만 설치 되었을 것이다.
대신 유령의존성과 100개의 lodash간의 의존성 충돌 문제가 발생할 가능성이 크다.
pnpm은 어떻게 동작하길래 빠르고 가벼울까?
pnpm은 npm의 느린 설치 속도와 큰 디스크 사용량 문제를 어떻게 해결했을까?
Hard-Link로 빠른 설치 속도 달성
pnpm은 Content-Addressable Store라는 개념을 도입했다.

pnpm-store는 기기마다 하나씩만 존재하며, 프로젝트가 달라도 재설치를 하지 않는다.
동일한 의존성이라면 프로젝트의 node_modules에서 하드링크로 store에 있는 의존성을 연결해서 쓴다.
즉 파일복사도 아니며, 여러 프로젝트에서 같은 물리파일을 참조한다.
pnpm은 머신에서 딱 한번만 설치된다는 뜻이다. 당연히 디스크 공간을 절약하며 중복도 제거된다.
또, lodash v1.0.0에서 1.0.1로 업데이트 될 때, 해당 의존성을 재설치하는 것이 아니라 변경된 파일만 저장하는 센스도 가지고 있다.
내 머신의 pnpm store는 어디있을까?
pnpm store path를 들어가보자.
결정적 설치
pnpm은 의존성 설치 속도에서부터 npm보다 2배 이상 빠르다고 알려져 있다.
기존 npm은 의존성 트리를 재귀적으로 탐색하며, 새로 설치한 패키지가 기존 트리와 충돌하지 않도록
무엇을 호이스팅(flatten) 해야 할지 계산해야 했기 때문에, 설치 과정에서 많은 연산과 I/O가 발생했다.
반면 pnpm은 이미 설치된 의존성이 있다면, 새로 다운로드하지 않고 전역 스토어에 있는 패키지를 하드링크로 연결만 한다.
또한 npm처럼 트리를 다시 계산하지 않고, 이미 확정된 pnpm-lock.yaml의 의존성 그래프를 그대로 따라가기 때문에 “무엇을 어디에 올릴지” 판단하는 과정 자체가 없다.
- 네트워크 요청이 줄고,
- 호이스팅 계산이 사라지며,
- lockfile 기반의 결정적 설치(deterministic install) 가 이루어져
결과적으로 pnpm은 이와 같은 이유로 npm보다 빠른 설치 속도를 자랑한다.
pnpm은 어떻게 유령의존성을 극복했나?
npm에서는 내가 설치하지 않은 lodash가 호이스팅되어,
require 덕분에 의존성을 가져다 쓰는 유령의존성문제가 있었다.
node_modules/
├── package-a/
├── package-b/
├── lodash/ → lodash는 package-a, package-b의 하위의존성에 불과함
즉 유령 의존성은 호이스팅과 require의 동작 때문인데, pnpm은 Node.js의 require 모듈 해석 방식을 그대로 따르면서도, 유령 의존성을 방지하기 위해 의존성 트리를 평탄화하지 않는 전략을 선택했다.
node_modules/
├── package-a → .pnpm/package-a@1.0.0/node_modules/package-a (symlink)
├── package-b → .pnpm/package-b@1.0.0/node_modules/package-b (symlink)
├── .pnpm/
│ ├── package-a@1.0.0/
│ │ └── node_modules/
│ │ ├── package-a/ (hardlink)
│ │ └── lodash@4.17.21/ (hardlink)
│ ├── package-b@1.0.0/
│ │ └── node_modules/
│ │ ├── package-b/ (hardlink)
│ │ └── lodash@3.10.1/ (hardlink)
node_modules에는 호이스팅 된 것은 없다. .pnpm이라는 폴더와 직접 설치한 의존성들만 존재한다.
.pnpm 폴더 밑에는 내가 설치한 의존성들마다, 독립된 node_modules 구조를 구성하고있는데,
직접 설치한 의존성들은 sym-link로 .pnpm 밑에 있는 자신의 의존성들을 바라보고 있게 만들었다.
즉, .pnpm 폴더로 격리하고, sym-link로 연결했기 때문에
기본 require로직은 그대로 유지하면서, 그리고 호이스팅이나 의존성 충돌이 일어날 일이 없고
내가 직접 설치한 의존성들만 사용할 수 있게 되었다.
Hard Link :
inode를 함께 바라보는 새로운 파일을 만드는 것, inode가 삭제되어도 데이터 블락을 가리키는 inode가 하나라도 존재한다면 데이터블락은 삭제되지 않는다.
Sym Link:
경로만 담고있는 특별한 파일로, 실행시키면 그 경로로 이동한다.
References
https://pnpm.io/motivation
https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way
https://po4tion.dev/pnpm